/**********************************************************************\

 Copyright 2012 Diggory Blake

 This file is part of klip-engine

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

\**********************************************************************/

#include "ksystemwindows.h"
#include "kperformance.h"
#include "kengine.h"
#include "kerror.h"

ATOM kSystemWindows::_windowClass = 0;
LRESULT CALLBACK kSystemWindows::staticWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	if (uMsg == WM_NCCREATE) {
		CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
		SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams);
		((kWindowWindows*)cs->lpCreateParams)->_hwnd = hwnd;
	}
	kWindowWindows* window = (kWindowWindows*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
	if (!window)
		return 0;

	switch (uMsg) {
	case WM_ACTIVATE:
		if (window->_options & keWindow_Fullscreen) {
			if (wParam) {
				DEVMODE dm;
				ZeroMemory(&dm, sizeof(dm));

				dm.dmSize = sizeof(dm);
				dm.dmBitsPerPel = 32;
				RECT rc;
				GetClientRect(hwnd, &rc);
				dm.dmPelsWidth = rc.right - rc.left;
				dm.dmPelsHeight = rc.bottom - rc.top;
				dm.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
				ChangeDisplaySettings(&dm, CDS_FULLSCREEN);
			} else {
				ShowWindow(hwnd, SW_MINIMIZE);
				ChangeDisplaySettings(0, 0);
			}
		}
		break;
	case WM_MOVE:
		if (window->onMove) {
			int x = LOWORD(lParam);
			int y = HIWORD(lParam);
			CALL_DELEGATE(window->onMove, window, kVec2i(x, y));
		}
		break;
	case WM_SIZE:
		if (wParam != SIZE_MINIMIZED)
			if (window->onResize) {
				int w = LOWORD(lParam);
				int h = HIWORD(lParam);
				CALL_DELEGATE(window->onResize, window, kVec2i(w, h));
			}
		break;
	case WM_CLOSE:
		{
			bool deleteWindow = true;
			if (window->onClose) {
				CALL_DELEGATE(window->onClose, window, &deleteWindow);
			}
			if (deleteWindow) {
				window->destroy();
			}
		}
		return 0;
	case WM_DESTROY:
		{
			bool quitApp = true;
			if (window->onDestroy) {
				CALL_DELEGATE(window->onDestroy, window, &quitApp);
			}
			delete window;
			if (quitApp) {
				PostQuitMessage(0);
			}
			break;
		}
	case WM_MOUSEMOVE:
		{
			if (!window->_mouseInput.getMouseOver()) {
				TRACKMOUSEEVENT tme = {0};
				tme.cbSize = sizeof(tme);
				tme.hwndTrack = hwnd;
				tme.dwFlags = TME_LEAVE;
				TrackMouseEvent(&tme);

				window->_mouseInput.updateMouseOver(true);
			}

			kVec2i pos(lParam & 0xFFFF, lParam >> 16);
			window->_mouseInput.updateMousePos(pos);
		}
		break;
	case WM_MOUSELEAVE:
		window->_mouseInput.updateMouseOver(false);
		break;
	case WM_LBUTTONDOWN:
		SetCapture(hwnd);
		window->_mouseInput.updateMouseButtons(keMouseButton_Left, true);
		break;
	case WM_RBUTTONDOWN:
		SetCapture(hwnd);
		window->_mouseInput.updateMouseButtons(keMouseButton_Right, true);
		break;
	case WM_MBUTTONDOWN:
		SetCapture(hwnd);
		window->_mouseInput.updateMouseButtons(keMouseButton_Middle, true);
		break;
	case WM_LBUTTONUP:
		ReleaseCapture();
		window->_mouseInput.updateMouseButtons(keMouseButton_Left, false);
		break;
	case WM_RBUTTONUP:
		ReleaseCapture();
		window->_mouseInput.updateMouseButtons(keMouseButton_Right, false);
		break;
	case WM_MBUTTONUP:
		ReleaseCapture();
		window->_mouseInput.updateMouseButtons(keMouseButton_Middle, false);
		break;
	case WM_MOUSEWHEEL:
		{
			int scroll = wParam >> 16;
			window->_mouseInput.updateMouseWheel(scroll);
		}
		break;
	case WM_KEYDOWN:
		if (wParam >= 0 && wParam < 256) {
			keKeyCode keyCode = (keKeyCode)wParam;
			window->_keyboardInput.updateKey(keyCode, true);
			int repeat = lParam & 0xFFFF;
			for (int i = 0; i < repeat; i++) {
				CALL_DELEGATE(window->_keyboardInput.onKeyPress, window, keyCode);
			}
		}
		break;
	case WM_KEYUP:
		if (wParam >= 0 && wParam < 256) {
			window->_keyboardInput.updateKey((keKeyCode)wParam, false);
		}
		break;
	case WM_CHAR:
		{
			int repeat = lParam & 0xFFFF;
			for (int i = 0; i < repeat; i++) {
				CALL_DELEGATE(window->_keyboardInput.onKeyChar, window, (tchar)wParam);
			}
		}
		break;
	}
	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void kSystemWindows::onUpdate(float delta) {
	PERF_QUERY;

	for (auto i = kWindowWindows::_windows.begin(); i != kWindowWindows::_windows.end(); i++) {
		(*i)->update();
	}

	MSG msg = {0};
	// Check to see if any messages are waiting in the queue
	while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
	{
		// If the message is WM_QUIT, tell the engine to quit
		if (msg.message == WM_QUIT)
			getEngine()->quit();

		// Translate the message and dispatch it to wndProc
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}
unsigned kSystemWindows::getTime() {
	return timeGetTime();
}
kWindow* kSystemWindows::createWindow(const std::tstring& title) {
	HINSTANCE instance = GetModuleHandle(0);
	if (!_windowClass) {
		WNDCLASSEX wndClass = {0};
		wndClass.cbSize = sizeof(wndClass);
		wndClass.hInstance = instance;
		wndClass.lpfnWndProc = &staticWndProc;
		wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
		wndClass.lpszClassName = _T("kSystemWindows");

		_windowClass = RegisterClassEx(&wndClass);
	}

	kWindowWindows* window = new kWindowWindows();
	CreateWindowEx(
		0, (LPCTSTR)_windowClass, title.c_str(), 0,
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, instance, window
		);

	if (!window->_hwnd) {
		delete window;
		throw kError(_T("Failed to create window!"));
	}

	return window;
}
DWORD kSystemWindows::getWindowStyle(keWindowOptions options) {
	DWORD result = WS_SYSMENU | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
	if (options & keWindow_Visible) {
		result |= WS_VISIBLE;
	}

	if ((options & keWindow_Fullscreen) || (options & keWindow_Borderless)) {
		result |= WS_POPUP;
	} else {
		result |= WS_OVERLAPPED | WS_CAPTION;

		if (options & keWindow_Resizable) {
			result |= WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
		}
	}


	return result;
}
DWORD kSystemWindows::getWindowStyleEx(keWindowOptions options) {
	DWORD result = WS_EX_APPWINDOW;

	return result;
}
void UnAdjustWindowRectEx(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle) {
	RECT rc = {0, 0, 0, 0};
	AdjustWindowRectEx(&rc, dwStyle, bMenu, dwExStyle);
	lpRect->left -= rc.left;
	lpRect->top -= rc.top;
	lpRect->right -= rc.right;
	lpRect->bottom -= rc.bottom;
}
void PreChangeWindow(HWND hwnd, WINDOWPLACEMENT* wp, DWORD* style, DWORD* exStyle) {
	wp->length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(hwnd, wp);
	*style = GetWindowLong(hwnd, GWL_STYLE);
	*exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
	UnAdjustWindowRectEx(&wp->rcNormalPosition, *style, 0, *exStyle);
}
void PostChangeWindow(HWND hwnd, WINDOWPLACEMENT* wp, DWORD* style, DWORD* exStyle) {
	wp->showCmd = SW_HIDE;
	
	AdjustWindowRectEx(&wp->rcNormalPosition, *style, 0, *exStyle);
	SetWindowLong(hwnd, GWL_STYLE, *style & ~WS_VISIBLE);
	SetWindowLong(hwnd, GWL_EXSTYLE, *exStyle);
	SetWindowPlacement(hwnd, wp);

	if (*style & WS_VISIBLE)
		ShowWindow(hwnd, SW_SHOW);
}

std::set<kWindowWindows*> kWindowWindows::_windows;
void kWindowWindows::update() {
	_mouseInput.preUpdate();
	_keyboardInput.preUpdate();
}
kHandle kWindowWindows::getHandle() {
	return _hwnd;
}
kMouseInput* kWindowWindows::getMouseInput() {
	return &_mouseInput;
}
kKeyboardInput* kWindowWindows::getKeyboardInput() {
	return &_keyboardInput;
}
void kWindowWindows::close() {
	SendMessage(_hwnd, WM_CLOSE, 0, 0);
}
void kWindowWindows::destroy() {
	DestroyWindow(_hwnd);
}
kHandle kWindowWindows::createGLContext() {
	HDC hdc = GetDC(_hwnd);

	PIXELFORMATDESCRIPTOR pfd;
	ZeroMemory(&pfd, sizeof(pfd));
	pfd.nSize = sizeof(pfd);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 24;
	pfd.iLayerType = PFD_MAIN_PLANE;
	int fmt = ChoosePixelFormat(hdc, &pfd);
	SetPixelFormat(hdc, fmt, &pfd);

	HGLRC context = wglCreateContext(hdc);
	wglMakeCurrent(hdc, context);

	return context;
}
void kWindowWindows::deleteGLContext(kHandle context) {
	wglDeleteContext((HGLRC)context);
}
void kWindowWindows::activateGLContext(kHandle context) {
	HDC hdc = GetDC(_hwnd);

	wglMakeCurrent(hdc, (HGLRC)context);
}
void kWindowWindows::setOptions(keWindowOptions options) {
	_options = options;

	WINDOWPLACEMENT wp;
	DWORD style, exStyle;
	PreChangeWindow(_hwnd, &wp, &style, &exStyle);
	style = kSystemWindows::getWindowStyle(options);
	exStyle = kSystemWindows::getWindowStyleEx(options);

	if (options & keWindow_Fullscreen) {
		wp.rcNormalPosition.right -= wp.rcNormalPosition.left;
		wp.rcNormalPosition.bottom -= wp.rcNormalPosition.top;
		wp.rcNormalPosition.left = 0;
		wp.rcNormalPosition.top = 0;
	}

	PostChangeWindow(_hwnd, &wp, &style, &exStyle);

	if (onChange) {
		CALL_DELEGATE(onChange, this, options);
	}
}
void kWindowWindows::setSize(const kVec2i& size) {
	WINDOWPLACEMENT wp;
	DWORD style, exStyle;
	PreChangeWindow(_hwnd, &wp, &style, &exStyle);
	wp.rcNormalPosition.right = wp.rcNormalPosition.left + size.x;
	wp.rcNormalPosition.bottom = wp.rcNormalPosition.top + size.y;
	PostChangeWindow(_hwnd, &wp, &style, &exStyle);
}
void kWindowWindows::setPos(const kVec2i& pos) {
	WINDOWPLACEMENT wp;
	DWORD style, exStyle;
	PreChangeWindow(_hwnd, &wp, &style, &exStyle);
	int width = wp.rcNormalPosition.right - wp.rcNormalPosition.left;
	int height = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top;
	wp.rcNormalPosition.left = pos.x;
	wp.rcNormalPosition.top = pos.y;
	wp.rcNormalPosition.right = pos.x + width;
	wp.rcNormalPosition.bottom = pos.y + height;
	PostChangeWindow(_hwnd, &wp, &style, &exStyle);
}
void kWindowWindows::setTitle(const std::tstring& title) {
	SetWindowText(_hwnd, title.c_str());
}
void kWindowWindows::swapGLBuffers() {
	HDC hdc = GetDC(_hwnd);

	SwapBuffers(hdc);
}
kVec2i kWindowWindows::getPos() {
	WINDOWPLACEMENT wp;
	DWORD style, exStyle;
	PreChangeWindow(_hwnd, &wp, &style, &exStyle);

	return kVec2i(wp.rcNormalPosition.left, wp.rcNormalPosition.top);
}
kVec2i kWindowWindows::getSize() {
	RECT rc;
	GetClientRect(_hwnd, &rc);

	return kVec2i(rc.right - rc.left, rc.bottom - rc.top);
}
keWindowOptions kWindowWindows::getOptions() {
	return _options;
}
